fix(tls): support custom CA certificates for corporate proxies (CLI-1K6)#901
Merged
fix(tls): support custom CA certificates for corporate proxies (CLI-1K6)#901
Conversation
Contributor
|
Contributor
Codecov Results 📊✅ 6521 passed | Total: 6521 | Pass Rate: 100% | Execution Time: 0ms 📊 Comparison with Base Branch
All tests are passing successfully. ❌ Patch coverage is 72.22%. Project has 13305 uncovered lines. Files with missing lines (5)
Coverage diff@@ Coverage Diff @@
## main #PR +/-##
==========================================
+ Coverage 75.99% 76.05% +0.06%
==========================================
Files 295 296 +1
Lines 55386 55555 +169
Branches 0 0 —
==========================================
+ Hits 42090 42250 +160
- Misses 13296 13305 +9
- Partials 0 0 —Generated by Codecov Action |
731c2f0 to
54ee5cb
Compare
54ee5cb to
fe6d962
Compare
Add custom CA certificate loading so the CLI works behind TLS-intercepting
corporate proxies (Zscaler, Netskope, etc.). Bun uses BoringSSL with a
compiled-in Mozilla CA bundle and does not honor NODE_EXTRA_CA_CERTS or
SSL_CERT_FILE natively — we now read these env vars and pass the PEM
contents to Bun's fetch({ tls: { ca } }) option.
CA loading priority: stored default > NODE_EXTRA_CA_CERTS > SSL_CERT_FILE.
Users can register a CA cert via 'sentry cli defaults ca-cert /path/to/ca.pem'
which also silences a security warning logged when env-sourced CAs target
sentry.io (the warning creates a forensic trail for CI environments where
an attacker could inject a rogue CA via env vars).
Also improves TLS error handling:
- Detect TLS cert errors by pattern and wrap in ApiError (not raw Error)
- Show actionable guidance pointing to 'sentry cli defaults ca-cert'
- Don't retry TLS errors (deterministic — retrying is pointless)
- Fix async race in resolve(): cache the promise instead of a boolean
flag so concurrent callers await the same I/O (Cursor Bugbot)
- Fix isTlsCertError to walk error.cause chain for Node.js fetch which
wraps TLS errors in TypeError('fetch failed') (Seer)
- Add tests for error.cause detection
- Remove CERT_HAS_EXPIRED and ERR_TLS_CERT_ALTNAME_INVALID from TLS patterns — not CA trust issues, would produce misleading guidance (Seer) - Extract root TLS error via getTlsCertErrorMessage() so Node.js 'fetch failed' wrappers don't hide the real error (Cursor Bugbot) - Wire getTlsCertErrorMessage into both sentry-client.ts and oauth.ts
c44337a to
acc094d
Compare
…CERT_FILE in tests
…() sync - Extract readCaCertFile() as single source of truth for PEM validation - defaults ca-cert handler now calls readCaCertFile instead of duplicating - resolve() is now sync (readFileSync), eliminating the async race entirely - getCustomTlsOptions() is no longer async — simpler call sites
- Add cycle detection (Set guard) in getTlsCertErrorMessage cause walker - Extract buildTlsErrorDetail to custom-ca.ts so both sentry-client.ts and oauth.ts share the hasCustomCa branching logic - Store resolvedLabel during resolve() so warnIfSaasWithEnvCa shows the actual source, not a re-read of env that may disagree - getCustomCaSource() now calls resolve() to prevent stale 'none' if called before getCustomTlsOptions()
Contributor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1e6603a. Configure here.
Bun's tls.ca replaces the default Mozilla CA bundle rather than supplementing it. Concatenate via [...rootCertificates, customPem] to match NODE_EXTRA_CA_CERTS additive behavior — without this, setting a corporate CA would break all public CA trust.
…ified experience - Drop SSL_CERT_FILE support (neither Bun nor Node honor it natively) - On Node 24+, call tls.setDefaultCACertificates() to inject custom CAs into the process-wide TLS trust store so Node's fetch() also works - On Node 22, NODE_EXTRA_CA_CERTS is handled natively by Node - Feature-detect setDefaultCACertificates (undefined on Node 22)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
fetch()doesn't honorNODE_EXTRA_CA_CERTSnatively — we now read it and pass CA certs via Bun'sfetch({ tls: { ca } })optiontls.setDefaultCACertificates()to inject CAs into the process-wide trust storesentry cli defaults ca-certfor persistent CA registration that also silences the SaaS security warningDetails
Root cause
Users behind corporate TLS proxies (Zscaler, Netskope, Palo Alto) get
Error: unable to get local issuer certificatebecause Bun uses BoringSSL with Mozilla's compiled-in CA bundle and doesn't readNODE_EXTRA_CA_CERTSor the OS certificate store.Fix
New
src/lib/custom-ca.tsmodule reads CA certs from (in priority order):sentry cli defaults ca-cert(stored in SQLite)NODE_EXTRA_CA_CERTSenv varCustom CAs are concatenated with
tls.rootCertificates(Mozilla's built-in bundle) to preserve additive semantics — only adding CAs, never replacing the default bundle.Runtime behavior:
fetch({ tls: { ca: combined } })— Bun-specific optiontls.setDefaultCACertificates([...rootCerts, custom])— process-wideNODE_EXTRA_CA_CERTShandled natively by Node;defaults ca-certrequires Node 24+PEM validation logic is shared via
readCaCertFile()— used by both the eager validation insentry cli defaults ca-certand the lazy loading at fetch time.Security model
When custom CAs come from env vars (not stored defaults) AND the target is
*.sentry.io, a one-timelog.warn()fires. This creates a forensic trail in CI logs — an attacker who can set env vars in a CI step could inject a rogue CA + proxy to intercept tokens.sentry cli defaults ca-certsilences the warning (user has acknowledged). See plan file for the full threat model discussion.Error handling improvements
error.causechain with cycle detection for Node.js'sTypeError: fetch failedwrapper) and wrapped inApiErrorbuildTlsErrorDetail()is shared between both fetch paths and branches on whether custom CAs are already loadedCERT_HAS_EXPIREDor hostname mismatches which need different guidance)Changes
src/lib/custom-ca.tsreadCaCertFile,buildTlsErrorDetail,isTlsCertError, Node 24+ injection, SaaS warningsrc/lib/sentry-client.tsfetchWithTimeout, TLS error handlingsrc/lib/oauth.tssrc/lib/db/defaults.tsdefaults.ca-certkeysrc/commands/cli/defaults.tsca-certhandler with aliases, eager PEM validationsrc/lib/formatters/human.ts"ca-cert": "CA Certificate"to defaults displaysrc/lib/env-registry.tsNODE_EXTRA_CA_CERTSscript/generate-docs-sections.tstest/lib/custom-ca.test.tsFixes CLI-1K6